using System;
using System.Collections;
using System.Diagnostics;
using System.Drawing;
using System.Globalization;
using System.Xml;
using System.Xml.XPath;

namespace FreeFlow.Client
{
	/// <summary>
	/// A form
	/// </summary>
	[Serializable]
  public class Form : Connectable
  {
    private System.Collections.Generic.List<Field> fields =
      new System.Collections.Generic.List<Field>();
    private System.Collections.Generic.List<ClientScript> scripts =
      new System.Collections.Generic.List<ClientScript>();

    /// <summary>The name of the XML element containing form layout information.</summary>
    public const string FormLayoutElement = "FormLayout";

    /// <summary>
    /// Creates a new <see cref="Form"/> instance.
    /// </summary>
    /// <param name="formLayoutNode">The FormLayout XML node used to populate the form.</param>
    /// <param name="getAttachment">The IGetAttachment implementation used to get attachments.</param>
    public Form(XmlElement formLayoutNode, IGetAttachment getAttachment)
    { 
      if (formLayoutNode != null)
      {
        name = formLayoutNode.GetAttribute("Form");

        if (formLayoutNode.GetAttribute(FreeFlow.Client.Field.ColourAttribute).Length > 0)
          color = FreeFlow.Client.Field.ParseColor(
            formLayoutNode.GetAttribute(FreeFlow.Client.Field.ColourAttribute));
        
        if (formLayoutNode.GetAttribute("Height").Length > 0)
          height = int.Parse(formLayoutNode.GetAttribute("Height"));
        if (formLayoutNode.GetAttribute("Width").Length > 0)
          width = int.Parse(formLayoutNode.GetAttribute("Width"));

        if (formLayoutNode.GetAttribute("Version").Length > 0)
          version = int.Parse(formLayoutNode.GetAttribute("Version"));

        XmlElement fontNode = formLayoutNode["Font"];
        if (fontNode != null)
        {
          font = FreeFlow.Client.Field.ParseFont(fontNode);
          fontColor = FreeFlow.Client.Field.ParseFontColor(fontNode);
        }
        
        procedure = formLayoutNode.GetAttribute("Procedure");

        // client scripts
        XmlElement scriptsNode = formLayoutNode["ScriptList"];
        if (scriptsNode != null)
        {
          foreach (XmlElement scriptNode in scriptsNode.ChildNodes)
          {
            ClientScript script = new ClientScript(scriptNode, getAttachment);

            scripts.Add(script);
          }
        }

        // read in fields
        XmlElement fieldsNode = formLayoutNode["FieldList"];
        if (fieldsNode != null)
        {
          foreach (XmlElement fieldNode in fieldsNode.ChildNodes)
          {
            Field field = CreateField(fieldNode, getAttachment);
            field.Connection = Connection;
            fields.Add(field);
          }
        }
      }
    }

    /// <summary>
    /// Gets or sets the connection.
    /// </summary>
    public override Connection Connection
    {
      get { return base.Connection; }
      set
      {
        base.Connection = value;

        // ensure fields have correct connection
        for (int i = 0; i < FieldCount; i++)
        {
          Field(i).Connection = Connection;
        }
      }
    }

    /// <summary>
    /// Fills the form from a field output list.
    /// </summary>
    /// <param name="fieldOutputList">Field output list.</param>
    public void FillValues(XmlElement fieldOutputList)
    {
      if (fieldOutputList != null)
      {
        foreach (XmlElement node in fieldOutputList.ChildNodes)
        {
          string fieldName = node.GetAttribute("Field");
          Field field = Field(fieldName);
          if (field != null)
          {
            field.FillValue(node);
          }
        }
      }
    }

    /// <summary>
    /// Fills the form fields from a field usage list.
    /// </summary>
    /// <param name="fieldUsageList">Field usage list.</param>
    public void FillUsage(XmlElement fieldUsageList)
    {
      if (fieldUsageList != null)
      {
        foreach (XmlElement usageNode in fieldUsageList)
        {
          Field thisField = Field(usageNode.GetAttribute("Field"));
          if (thisField != null)
            thisField.FillUsage(usageNode);
        }
      }
    }

    /// <summary>
    /// Ensures all fields hasve valid values.
    /// </summary>
    public void Validate()
    {
      for (int i = 0; i < FieldCount; i++)
      {
        Field(i).Validate();
      }
    }

    /// <summary>
    /// Adds a field input list to an XML document.
    /// </summary>
    /// <param name="node">XML node.</param>
    public void AddFieldInputList(XmlElement node)
    {
      if (node != null)
      {
        XmlElement fieldValues = node.OwnerDocument.CreateElement("FieldInputList");
        for (int i=0; i<FieldCount; i++)
        {
          Field thisField = Field(i);
          if (thisField.ShouldAddToFieldInput() && (thisField.Usage != FieldUsage.Hidden))
          {
            XmlElement field = node.OwnerDocument.CreateElement("FieldInput");
            field.SetAttribute("Field", thisField.Name);
            if (thisField.Value != null)
              field.InnerText = thisField.Value;
            fieldValues.AppendChild(field);
          }
        }
        node.AppendChild(fieldValues);
      }
    }

    #region form properties

    private string name;
    /// <summary>
    /// Gets the name of the form.
    /// </summary>
    public string Name
    {
      get { return name; }
    }

    private Font font;
    /// <summary>
    /// Gets the font for this form.
    /// </summary>
    public Font Font
    {
      get { return font; }
    }

    private Color fontColor;
    /// <summary>
    /// Gets the color of the form font.
    /// </summary>
    public Color FontColor
    {
      get { return fontColor; }
    }

    private Color color;
    /// <summary>
    /// Gets the color of the form.
    /// </summary>
    public Color Color
    {
      get { return color; }
    }

    private int height;
    /// <summary>
    /// Gets the height of the form.
    /// </summary>
    public int Height
    {
      get { return height; }
    }

    private int width;
    /// <summary>
    /// Gets the width of the form.
    /// </summary>
    public int Width
    {
      get { return width; }
    }

    /// <summary>
    /// Gets the size of the form.
    /// </summary>
    public Size Size
    {
      get
      {
        return new Size(width, height);
      }
    }

    private int version;
    /// <summary>
    /// Gets the version of the form.
    /// </summary>
    public int Version
    {
      get { return version; }
    }

    private string procedure;
    /// <summary>
    /// Gets the procedure that this form is part of.
    /// </summary>
    public string Procedure
    {
      get { return procedure; }
    }

    private IFolder page;
    /// <summary>
    /// Gets or sets the page of which this form is part.
    /// </summary>
    public IFolder Page
    {
      get { return page; }
      set { page = value; }
    }

    #endregion
    #region fields

    private Field CreateField(XmlElement fieldNode, IGetAttachment getAttachment)
    {
      switch (fieldNode.Name)
      {
        case "Label" : return new LabelField(this, fieldNode);
        case "Rule" : return new RuleField(this, fieldNode);
        case "Image": return new ImageField(this, fieldNode, getAttachment);
        case "Text": return new TextField(this, fieldNode);
        case "Memo": return new MemoField(this, fieldNode);
        case "DropDown": return new DropDownField(this, fieldNode);
        case "Grid": return new GridField(this, fieldNode);
        case "DateTime": return new DateTimeField(this, fieldNode);
        case "Number": return new NumberField(this, fieldNode);
        case "Button": return new ButtonField(this, fieldNode);
        case "Check": return new CheckField(this, fieldNode);
        case "Currency": return new CurrencyField(this, fieldNode);
        case "List": return new ListField(this, fieldNode);
        case "RadioGroup": return new RadioGroupField(this, fieldNode);
        case "Status": return new StatusField(this, fieldNode);
        case "Clip": return new AttachmentField(this, fieldNode);
        case "Signature": return new SignatureField(this, fieldNode);
        default : 
          throw new FreeFlowException("Unrecognized field type - " + fieldNode.Name);
      }   
    }

    /// <summary>
    /// Gets the number of fields in this form.
    /// </summary>
    public int FieldCount
    {
      get
      {
        return fields.Count;
      }
    }

    /// <summary>
    /// Returns the field at the specified index.
    /// </summary>
    /// <param name="index">Index.</param>
    /// <returns></returns>
    public Field Field(int index)
    {
      return fields[index] as Field; 
    }

    /// <summary>
    /// Returns the field with the specified name.
    /// </summary>
    /// <param name="fieldName">Name of the field.</param>
    public Field Field(string fieldName)
    {
      foreach (Field field in fields)
      {
        if (string.Compare(field.Name, fieldName, true) == 0)
          return field;
      }

      return null;
    }

    #endregion
    #region client scripts
    /// <summary>
    /// Gets the number of client scripts for this form.
    /// </summary>
    public int ScriptCount
    {
      get
      {
        return scripts.Count;
      }
    }

    /// <summary>
    /// Gets the client script at the specified index.
    /// </summary>
    /// <param name="index">Index.</param>
    public ClientScript Script(int index)
    {
      return (ClientScript)scripts[index];
    }
    #endregion
	}
}
